<?php

namespace maoxuner\LaravelJsonRpc\Entities;

use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Validator;
use maoxuner\LaravelJsonRpc\Exceptions\ServerException;
use maoxuner\LaravelJsonRpc\Rules\Identifier;
use Throwable;

class ResponseObject extends EntityObject
{
    public string $jsonrpc = '2.0';

    public mixed $result;

    public ?ErrorObject $error = null;

    /**
     * @var string|int|null
     */
    public mixed $id;

    public function __construct(array $attributes = [])
    {
        if (isset($attributes['error'])) {
            $attributes['error'] = new ErrorObject($attributes['error']);
        }
        parent::__construct($attributes);
    }

    public static function build($payload, $id = null): static
    {
        $instance = new static;

        if ($payload instanceof Throwable) {
            $instance->error = ErrorObject::build($payload);
        } elseif ($payload instanceof ErrorObject) {
            $instance->error = $payload;
        } else {
            $instance->result = $payload;
        }
        $instance->id = $id;

        return $instance;
    }

    /**
     * @throws ServerException
     * @throws \Illuminate\Validation\ValidationException
     */
    public static function validate(array $data): array
    {
        $rules = [
            'jsonrpc' => 'required|string|in:2.0',
            'id' => [
                'present',
                'id' => new Identifier,
            ],
        ];
        if (isset($data['error'])) {
            $rules['error'] = function (string $attribute, mixed $value, Closure $fail) {
                try {
                    ErrorObject::validate($value);
                } catch (Throwable) {
                    $fail('The {$attribute} is invalid.');
                }
            };
        } else {
            $rules['result'] = 'present';
        }
        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            throw (new ServerException('Response error'))
                ->fill($validator->getMessageBag()->getMessages());
        }

        return $validator->validated();
    }

    public function toArray()
    {
        $data = [
            'jsonrpc' => $this->jsonrpc,
        ];
        if ($this->error) {
            $data['error'] = $this->error->toArray();
        } else {
            if (is_null($this->result) || is_scalar($this->result)) {
                $data['result'] = $this->result;
            } else {
                $request = new Request;
                $request->headers->set('Accept', 'application/json');
                $response = Router::toResponse($request, $this->result);
                $data['result'] = $response instanceof JsonResponse ? $response->getData() : $this->result;
            }
        }
        if ($this->id) {
            $data['id'] = $this->id;
        }

        return $data;
    }
}
